#include "postprocess_op.h"

namespace ai {

void OCRPostProcessor::GetContourArea(const std::vector<std::vector<float>> &box,
                                   float unclip_ratio, float &distance) {
  int pts_num = 4;
  float area = 0.0f;
  float dist = 0.0f;
  for (int i = 0; i < pts_num; i++) {
    area += box[i][0] * box[(i + 1) % pts_num][1] -
            box[i][1] * box[(i + 1) % pts_num][0];
    dist += sqrtf((box[i][0] - box[(i + 1) % pts_num][0]) *
                      (box[i][0] - box[(i + 1) % pts_num][0]) +
                  (box[i][1] - box[(i + 1) % pts_num][1]) *
                      (box[i][1] - box[(i + 1) % pts_num][1]));
  }
  area = fabs(float(area / 2.0));

  distance = area * unclip_ratio / dist;
}

cv::RotatedRect OCRPostProcessor::UnClip(std::vector<std::vector<float>> box,
                                      const float &unclip_ratio) {
  float distance = 1.0;

  GetContourArea(box, unclip_ratio, distance);

  ClipperLib::ClipperOffset offset;
  ClipperLib::Path p;
  p << ClipperLib::IntPoint(int(box[0][0]), int(box[0][1]))
    << ClipperLib::IntPoint(int(box[1][0]), int(box[1][1]))
    << ClipperLib::IntPoint(int(box[2][0]), int(box[2][1]))
    << ClipperLib::IntPoint(int(box[3][0]), int(box[3][1]));
  offset.AddPath(p, ClipperLib::jtRound, ClipperLib::etClosedPolygon);

  ClipperLib::Paths soln;
  offset.Execute(soln, distance);
  std::vector<cv::Point2f> points;

  for (int j = 0; j < soln.size(); j++) {
    for (int i = 0; i < soln[soln.size() - 1].size(); i++) {
      points.emplace_back(soln[j][i].X, soln[j][i].Y);
    }
  }
  cv::RotatedRect res;
  if (points.size() <= 0) {
    res = cv::RotatedRect(cv::Point2f(0, 0), cv::Size2f(1, 1), 0);
  } else {
    res = cv::minAreaRect(points);
  }
  return res;
}

float **OCRPostProcessor::Mat2Vec(cv::Mat mat) {
  auto **array = new float *[mat.rows];
  for (int i = 0; i < mat.rows; ++i)
    array[i] = new float[mat.cols];
  for (int i = 0; i < mat.rows; ++i) {
    for (int j = 0; j < mat.cols; ++j) {
      array[i][j] = mat.at<float>(i, j);
    }
  }

  return array;
}

int OCRPostProcessor::OrderPointsClockwise(std::vector<cv::Point> &pts) {
  std::sort(pts.begin(), pts.end(), [](cv::Point &a, cv::Point &b){
    return a.x < b.x;
  });

  std::vector<cv::Point> leftmost = {pts[0], pts[1]};
  std::vector<cv::Point> rightmost = {pts[2], pts[3]};

  if (leftmost[0].y > leftmost[1].y)
    std::swap(leftmost[0], leftmost[1]);

  if (rightmost[0].y > rightmost[1].y)
    std::swap(rightmost[0], rightmost[1]);

  // 0 1
  // 3 2
  pts[0] = leftmost[0];
  pts[1] = rightmost[0];
  pts[2] = rightmost[1];
  pts[3] = leftmost[1];

  return 0;
}

std::vector<std::vector<float>> OCRPostProcessor::Mat2Vector(cv::Mat mat) {
  std::vector<std::vector<float>> img_vec;
  std::vector<float> tmp;

  for (int i = 0; i < mat.rows; ++i) {
    tmp.clear();
    for (int j = 0; j < mat.cols; ++j) {
      tmp.push_back(mat.at<float>(i, j));
    }
    img_vec.push_back(tmp);
  }
  return img_vec;
}

bool OCRPostProcessor::XsortFp32(std::vector<float> a, std::vector<float> b) {
  if (a[0] != b[0])
    return a[0] < b[0];
  return false;
}

bool OCRPostProcessor::XsortInt(std::vector<int> a, std::vector<int> b) {
  if (a[0] != b[0])
    return a[0] < b[0];
  return false;
}

std::vector<std::vector<float>> OCRPostProcessor::GetMiniBoxes(cv::RotatedRect box,
                                                            float &ssid) {
  ssid = std::max(box.size.width, box.size.height);

  cv::Mat points;
  cv::boxPoints(box, points);

  auto array = Mat2Vector(points);
  std::sort(array.begin(), array.end(), XsortFp32);

  std::vector<float> idx1 = array[0], idx2 = array[1], idx3 = array[2],
                     idx4 = array[3];
  if (array[3][1] <= array[2][1]) {
    idx2 = array[3];
    idx3 = array[2];
  } else {
    idx2 = array[2];
    idx3 = array[3];
  }
  if (array[1][1] <= array[0][1]) {
    idx1 = array[1];
    idx4 = array[0];
  } else {
    idx1 = array[0];
    idx4 = array[1];
  }

  array[0] = idx1;
  array[1] = idx2;
  array[2] = idx3;
  array[3] = idx4;

  return array;
}

float OCRPostProcessor::BoxScoreFast(std::vector<std::vector<float>> box_array,
                                  cv::Mat pred) {
  auto array = box_array;
  int width = pred.cols;
  int height = pred.rows;

  float box_x[4] = {array[0][0], array[1][0], array[2][0], array[3][0]};
  float box_y[4] = {array[0][1], array[1][1], array[2][1], array[3][1]};

  int xmin = clamp(int(std::floor(*(std::min_element(box_x, box_x + 4)))), 0,
                   width - 1);
  int xmax = clamp(int(std::ceil(*(std::max_element(box_x, box_x + 4)))), 0,
                   width - 1);
  int ymin = clamp(int(std::floor(*(std::min_element(box_y, box_y + 4)))), 0,
                   height - 1);
  int ymax = clamp(int(std::ceil(*(std::max_element(box_y, box_y + 4)))), 0,
                   height - 1);

  cv::Mat mask;
  mask = cv::Mat::zeros(ymax - ymin + 1, xmax - xmin + 1, CV_8UC1);

  cv::Point root_point[4];
  root_point[0] = cv::Point(int(array[0][0]) - xmin, int(array[0][1]) - ymin);
  root_point[1] = cv::Point(int(array[1][0]) - xmin, int(array[1][1]) - ymin);
  root_point[2] = cv::Point(int(array[2][0]) - xmin, int(array[2][1]) - ymin);
  root_point[3] = cv::Point(int(array[3][0]) - xmin, int(array[3][1]) - ymin);
  const cv::Point *ppt[1] = {root_point};
  int npt[] = {4};
  cv::fillPoly(mask, ppt, npt, 1, cv::Scalar(1));

  cv::Mat croppedImg;
  pred(cv::Rect(xmin, ymin, xmax - xmin + 1, ymax - ymin + 1))
      .copyTo(croppedImg);

  auto score = cv::mean(croppedImg, mask)[0];
  return score;
}

int OCRPostProcessor::BoxesFromBitmap(std::vector < TextDetInfo > &text_lines,
                              const cv::Mat pred, 
                              const cv::Mat bitmap,
                              const float &box_thresh,
                              const float &det_db_unclip_ratio,
                              const float &det_expand_by_min_score) {
  int ret=0;
  const int max_candidates = 1000;

  std::vector<std::vector<cv::Point>> contours;
  std::vector<cv::Vec4i> hierarchy;

  cv::findContours(bitmap, contours, hierarchy, cv::RETR_LIST,
                   cv::CHAIN_APPROX_SIMPLE);

  int num_contours =
      contours.size() >= max_candidates ? max_candidates : contours.size();

  for (int _i = 0; _i < num_contours; _i++) {
    if (contours[_i].size() <= 2) {
      continue;
    }

    int area = cv::contourArea(contours[_i]);
    if (area <= 6 ) { continue; }
    // cv::Mat draw_contours = cv::Mat::zeros(pred.size(), CV_8UC1);
    // cv::drawContours(draw_contours, contours, _i, cv::Scalar(255), 1);
    // cv::imwrite(std::to_string(_i) + "_contours.jpg", draw_contours);
    
    std::vector<cv::Point> four_points;
    float score;
    if ((ret = unclip_for_points(contours[_i], det_db_unclip_ratio,
                                  box_thresh, bitmap, pred,four_points,
                                  score)) !=0 ){continue;}
    // 增加unclip
    // std::cout << "score: " << score << std::endl;
    std::vector<std::vector<cv::Point>> proposal_points;
    // if(score < det_expand_by_min_score){
      
    //   for (int i=-10;i< 18;i++){
    //   std::vector<cv::Point> four_points_b;
    //   float score_b;
    //   ret = unclip_for_points(contours[_i], det_db_unclip_ratio - i * 0.1,
    //                               box_thresh, bitmap, pred,four_points_b,
    //                               score_b);
    //   proposal_points.push_back(four_points_b);
    //   }
    // }
    // std::cout << four_points[0] << "  " << four_points[1] << "  " << four_points[2] << "  " << four_points[3] << std::endl;
    text_lines.push_back(TextDetInfo(four_points, score, proposal_points));

  } // end for
  return 0;
}

int OCRPostProcessor::unclip_for_points(std::vector<cv::Point> &contour, 
                                    const float &det_db_unclip_ratio,
                                    const float &box_thresh,
                                    const cv::Mat &bitmap,
                                    const cv::Mat &pred,
                                    std::vector<cv::Point> &four_points,
                                    float &score,
                                    bool extra_mode)
{
  float ssid;
  const int min_size = 3;

  int width = bitmap.cols;
  int height = bitmap.rows;

  cv::RotatedRect box = cv::minAreaRect(contour);
  auto array = GetMiniBoxes(box, ssid);

  auto box_for_unclip = array;
  // end get_mini_box

  if (ssid < min_size) {
    return -1;
  }

  if (!extra_mode){
    score = BoxScoreFast(array, pred);
    if (score < box_thresh)
      return -2;
  }

    // start for unclip
  cv::RotatedRect points = UnClip(box_for_unclip, det_db_unclip_ratio);
  if (points.size.height < 1.001 && points.size.width < 1.001) {
    return -3;
  }
  // end for unclip

  cv::RotatedRect clipbox = points;
  auto cliparray = GetMiniBoxes(clipbox, ssid);

  if (ssid < min_size + 2)
    return -4;

  int dest_width = pred.cols;
  int dest_height = pred.rows;

  for (int num_pt = 0; num_pt < 4; num_pt++) {
    cv::Point p{int(clampf(roundf(cliparray[num_pt][0] / float(width) *
                                          float(dest_width)),
                                  0, float(dest_width))),
                        int(clampf(roundf(cliparray[num_pt][1] /
                                          float(height) * float(dest_height)),
                                  0, float(dest_height)))};
    four_points.push_back(p);
  }
  return 0;
}

int OCRPostProcessor::FilterTagDetRes(std::vector < TextDetInfo > &text_lines,
                               float ratio_h, float ratio_w, cv::Mat srcimg) {
  int ret = 0;
  int oriimg_h = srcimg.rows;
  int oriimg_w = srcimg.cols;

  for (int n = 0; n < text_lines.size(); n++) {
    ret = OrderPointsClockwise(text_lines[n].four_points);
    for (int m = 0; m < text_lines[n].four_points.size(); m++) {
      float x = text_lines[n].four_points[m].x / ratio_w;
      float y = text_lines[n].four_points[m].y / ratio_h;

      text_lines[n].four_points[m] = \
                cv::Point(int(_min(_max(x, 0), oriimg_w - 1)),
                          int(_min(_max(y, 0), oriimg_h - 1)));
    }
    // std::cout << "OrderPointsClockwise:  " << text_lines[n].four_points[0] << "  " << text_lines[n].four_points[1] << "  " << text_lines[n].four_points[2] << "  " << text_lines[n].four_points[3] << std::endl;

  }

  for (std::vector < TextDetInfo >::iterator it=text_lines.begin(); \
                                            it!=text_lines.end();) {
    int rect_width, rect_height;
    rect_width = int(sqrt(pow(it->four_points[0].x - it->four_points[1].x, 2) +
                          pow(it->four_points[0].y - it->four_points[1].y, 2)));
    rect_height = int(sqrt(pow(it->four_points[0].x - it->four_points[3].x, 2) +
                           pow(it->four_points[0].y - it->four_points[3].y, 2)));
    // std::cout << "rect_width: " << rect_width << "  rect_height: " << rect_height << std::endl; 
    if (rect_width <= 10 || rect_height <= 10){
      it = text_lines.erase(it);
    }else{
      it++;
    }
  }
  return 0;
}


void Stringsplit(const string& str, const string& split, vector<string>& res)
{
	char* strc = new char[str.size() + 1];
	strcpy(strc, str.c_str());   // 将str拷贝到 char类型的strc中
	char* temp = strtok(strc, split.c_str());
	while (temp != NULL)
	{
		res.push_back(string(temp));		
		temp = strtok(NULL, split.c_str());	// 下一个被分割的串
	}
	delete[] strc;
}

void numarrbitcmp(int* arr,int* arr2,int len,bool* &res)
{
  //printf("do numarrbitcmp fun.\n");
  for(int i=0;i<len;i++)
  {
    if(arr[i]!=arr2[i])
    {
       res[i]=true;
    }else
    {
       res[i]=false;
    }
    //printf("%d ",res[i]);
  }
  //printf("\n");

}

void numandarrbitcmp(int* arr,int val,int len,bool* &res)
{
  //printf("do numandarrbitcmp fun.\n");
  for(int i=0;i<len;i++)
  {

    if(arr[i]!=val)
    {
       res[i]=true;
    }
    else
    {
      res[i]=false;
    }
    //printf("%d ",res[i]);
  }
  //printf("\n");

}

void arrbit_and_op(bool* arr,bool* arr2,int len,bool* &res)
{

  //printf("do arrbit_and_op fun.\n");
  for(int i=0;i<len;i++)
  {
    if(arr[i]&arr2[i])
    {
       res[i]=true;
    }else
    {
       res[i]=false;
    }
    //printf("%d ",res[i]);
  }
  //printf("\n");

}

void PlateReco_baidu_model(std::vector<float> &labels,std::vector<float> &plate_confidence, int* idarr, float* confarr)
{

    int idarr_rm_fist[14]={-1};
    int idarr_rm_lst[14]={-1};
    for (int i=0;i<14;i++)
    {
       idarr_rm_fist[i]=idarr[i+1];
    }
    for (int i=0;i<14;i++)
    {
       idarr_rm_lst[i]=idarr[i];
    }

    //bool res[14]={false};
    bool* res=new bool[14];
    numarrbitcmp(idarr_rm_fist,idarr_rm_lst,14,res);
    bool selection_tmp[15];
    //printf("selection.\n");
    for(int i=0;i<15;i++)
    {
       if (i==0)
       {
          selection_tmp[i]=true;
       }
       else
       {
          selection_tmp[i]=res[i-1];
       }
       //printf("%d ",selection_tmp[i]);
    }
    //printf("\n");
    bool* res2=new bool[15];
    numandarrbitcmp(idarr,0,15,res2);
    //printf("selection..\n");
    bool* selection=new bool[15];
    arrbit_and_op(selection_tmp,res2,15,selection);
    for(int i=0;i<15;i++)
    {
      if(selection[i])
      {
        labels.push_back(idarr[i]);
        plate_confidence.push_back(confarr[i]);
        //printf("%d ",idarr[i]);
      }
    }
    //printf("\n");

    delete [] res;
    delete [] res2;
    delete [] selection;


}

void Plate_wordpos_det_baidu_model(cv::Mat imgmap,int dest_width,int dest_height,std::vector<std::vector<cv::Point>> &poses)
{

    vector<vector<cv::Point>> contours;
    findContours(imgmap,contours,cv::noArray(),cv::RETR_LIST,cv::CHAIN_APPROX_SIMPLE);
    vector<vector<cv::Point2f>> bounding_boxes;
    for(int i=0;i<contours.size();i++)
    {
       vector<cv::Point> contour=contours[i];
       cv::RotatedRect bounding_box=minAreaRect(contour);
       if(bounding_box.size.width<3 || bounding_box.size.height<3)
       {
         continue;
       }
       cv::Point2f ps[4];
       bounding_box.points(ps);
       //printf("(%d %d) (%d %d) (%d %d) (%d %d)\n",(int)ps[0].x,(int)ps[0].y,(int)ps[1].x,(int)ps[1].y,(int)ps[2].x,(int)ps[2].y,(int)ps[3].x,(int)ps[3].y);
       vector<cv::Point2f> tmp;
       tmp.push_back(ps[0]);
       tmp.push_back(ps[1]);
       tmp.push_back(ps[2]);
       tmp.push_back(ps[3]);
       bounding_boxes.push_back(tmp);
    }

    for(int i=0;i<bounding_boxes.size();i++)
    {
      vector<cv::Point2f> ps = bounding_boxes[i];
      vector<cv::Point> contour;
      contour.push_back(cv::Point((int)ps[0].x,(int)ps[0].y));
      contour.push_back(cv::Point((int)ps[1].x,(int)ps[1].y));
      contour.push_back(cv::Point((int)ps[2].x,(int)ps[2].y));
      contour.push_back(cv::Point((int)ps[3].x,(int)ps[3].y));
      float poly_area=cv::contourArea(contour);
      float poly_len=cv::arcLength(contour,true);
      float unclip_ratio=1.2;
      float distance=poly_area*unclip_ratio/poly_len;
      //printf("distance:%.4f %.4f %.4f\n",distance,poly_area,poly_len);

      ClipperLib::Path pts;
      
      pts.push_back(ClipperLib::IntPoint(ps[0].x, ps[0].y));
      pts.push_back(ClipperLib::IntPoint(ps[1].x, ps[1].y));
      pts.push_back(ClipperLib::IntPoint(ps[2].x, ps[2].y));
      pts.push_back(ClipperLib::IntPoint(ps[3].x, ps[3].y));

      ClipperLib::ClipperOffset offset;
      offset.AddPath(pts, ClipperLib::jtRound, ClipperLib::etClosedPolygon);
      ClipperLib::Paths out;
      offset.Execute(out,distance);
      //printf("out.size:%d\n",out.size());
      vector<cv::Point> contour2;
      for(int i=0;i<out[0].size();i++)
      {
        ClipperLib::IntPoint pt=out[0][i];
        //printf("pt.X:%d pt.Y:%d\n",pt.X,pt.Y);
	    contour2.push_back(cv::Point((int)pt.X,(int)pt.Y));
      }

      cv::RotatedRect bounding_box2=minAreaRect(contour2);
      if(bounding_box2.size.width<5 || bounding_box2.size.height<5)
      {
         continue;
      }
      cv::Point2f ps2[4];
      bounding_box2.points(ps2);      
      //printf("(%d %d) (%d %d) (%d %d) (%d %d)\n",(int)ps2[0].x,(int)ps2[0].y,(int)ps2[1].x,(int)ps2[1].y,(int)ps2[2].x,(int)ps2[2].y,(int)ps2[3].x,(int)ps2[3].y);
      int width=imgmap.cols;
      int height=imgmap.rows;
      vector<cv::Point> ps3;
      for(int i=0;i<4;i++)
      {
        float ptx=ps2[i].x/width*dest_width;//将坐标点位映射到缩放前的原始图
        float pty=ps2[i].y/height*dest_height;//将坐标点位映射到缩放前的原始图
        
        // float ptx=ps2[i].x;
        // float pty=ps2[i].y;
        ps3.push_back(cv::Point(ptx,pty));
      }
      //printf("(%d %d) (%d %d) (%d %d) (%d %d)\n",(int)ps3[0].x,(int)ps3[0].y,(int)ps3[1].x,(int)ps3[1].y,(int)ps3[2].x,(int)ps3[2].y,(int)ps3[3].x,(int)ps3[3].y);
      
      poses.push_back(ps3);

    }    

}

} // namespace 
